﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Universe.RedisTests.WindowsRedisServer
{
    using System.Diagnostics;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    using System.Reflection;
    using System.Threading;

    using StackExchange.Redis;

    class RedisServerLauncher
    {
        public static int TcpPort = 6379;
        private static bool _IsAllowedOnFirewall;
        static private Process TheProcess;
        private static bool _IsLaunched = false;
        
        public static void Launch()
        {
            if (Environment.OSVersion.Platform != PlatformID.Win32NT)
                return;

            if (_IsLaunched) return;

            TcpPort = 0; GetTcpPort();
            // Trace.WriteLine("Working Redis Server Port: " + TcpPort);
            
            string path;
            Extract(out path);
            string exe = Path.Combine(path, "redis-server.exe");
            string arg = Path.Combine(path, "redis.windows.conf");

            AppDomain.CurrentDomain.DomainUnload += (sender, args) =>
            {
                Shutdown();
                Kill();
            };



            if (!_IsAllowedOnFirewall)
            {
                string netshArgs =
                    string.Format(
                        "firewall add allowedprogram \"{0}\" \"{1}\" ENABLE",
                        exe,
                        "Redis Server at " + Path.GetDirectoryName(exe)
                        );

                ProcessStartInfo siEnable = new ProcessStartInfo("netsh", netshArgs) {WindowStyle = ProcessWindowStyle.Hidden};
                using (var p = Process.Start(siEnable))
                {
                    p.WaitForExit();
                    Trace.WriteLine("Redis server is ENABLED: " + exe);
                }
            }

            ProcessStartInfo siServer = new ProcessStartInfo(exe, '"' + arg + '"') { WindowStyle = ProcessWindowStyle.Minimized };
            _IsLaunched = true;
            TheProcess = Process.Start(siServer);
        }

        static int GetTcpPort()
        {
            if (TcpPort == 0)
            {
                TcpListener l = new TcpListener(IPAddress.Loopback, 0);
                l.Start();
                int port = ((IPEndPoint)l.LocalEndpoint).Port;
                l.Stop();
                TcpPort = port;
                Trace.WriteLine("Redis Server Port is: " + port);
                return port;
            }

            return TcpPort;
        }


        static void Extract(out string folder)
        {
            string name = "redis-server.exe";
            var s = Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(RedisServerLauncher), name);
            if (s == null)
                throw new InvalidOperationException("Resource " + name + " not found");


            folder = Path.Combine(Path.GetTempPath(), "Temp Redis Server");
            if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);

            string file = Path.Combine(folder, name);
            using (s)
            using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
            {
                s.CopyTo(fs);
            }

            name = "redis.windows.conf";
            s = Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(RedisServerLauncher), name);
            if (s == null)
                throw new InvalidOperationException("Resource " + name + " not found");


            file = Path.Combine(folder, name);
            using (s)
            using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
            using (StreamReader rdr = new StreamReader(s))
            using (StreamWriter wr = new StreamWriter(fs))
            {
                string cfg = "port " + TcpPort + Environment.NewLine + rdr.ReadToEnd();
                wr.WriteLine(cfg);
            }


        }

        static void Shutdown()
        {
            Stopwatch startAt = Stopwatch.StartNew();

            string hostAndPort = "127.0.0.1:" + RedisServerLauncher.TcpPort;
            var config = new ConfigurationOptions
            {
                AbortOnConnectFail = false,
                ResolveDns = false,
                AllowAdmin = true,
                EndPoints = { hostAndPort },
                ConnectTimeout = 5000,
            };

            try
            {
                var log = new StringWriter();
                using (ConnectionMultiplexer conn = ConnectionMultiplexer.Connect(config, log))
                {
                    bool isOk = conn.IsConnected;
                    if (!isOk)
                    {
                        Trace.WriteLine(log);
                    }

                    var server = conn.GetServer("127.0.0.1", RedisServerLauncher.TcpPort);
                    if (server != null)
                    {
                        server.Save(SaveType.BackgroundSave);
                        server.Shutdown(ShutdownMode.Always);
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Shutdown is incomplete: " + Environment.NewLine + ex);
            }
        }


        public static void Kill()
        {
            // return;
            if (Environment.OSVersion.Platform != PlatformID.Win32NT)
                return;

            if (TheProcess != null)
            {
                try
                {
                    TheProcess.Kill();
                }
                catch (Exception)
                {
                }
            }

            for (int i = 0; i < 5; i++)
            {
                ProcessStartInfo si = new ProcessStartInfo("taskkill.exe", "/f /im redis-server.exe")
                {
                    WindowStyle = ProcessWindowStyle.Hidden
                };

                using (Process.Start(si))
                {
                }
            }

        }
    }
}
